<?php

declare(strict_types=1);

namespace RX\Component\Cache;

use EasySwoole\EasySwoole\Task\TaskManager;
use EasySwoole\Redis\Config\RedisConfig;
use EasySwoole\Redis\Redis as ESRedis;
use EasySwoole\Redis\Redis as RedisClient;
use EasySwoole\RedisPool\RedisPool;
use RX\Helpers\RandomHelper;

class RedisCache
{
    private static $name = "default";

    public static function setName(string $name)
    {
        self::$name = $name;
        return (new self());
    }

    public static function defer(string $name = 'default', $timeout = null): ?RedisClient
    {
        return RedisPool::defer($name, $timeout);
    }

    public static function invoke(callable $callback, $timeout = null)
    {
        return RedisPool::invoke($callback, self::$name, $timeout = null);
    }


    /********************************************** String ************************************************************/
    /**
     * 检测key是否存在
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:48 上午
     * @param string $key key
     * @return mixed|null
     */
    public static function exists(string $key)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key) {
            return $redis->exists($key);
        }, self::$name);
    }


    /**
     * 获取key
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:49 上午
     * @param string $key key
     * @return mixed|null
     */
    public static function get(string $key)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key) {
            return $redis->get($key);
        }, self::$name);
    }


    /**
     * 设置key
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:49 上午
     * @param string $key key
     * @param $value 数据
     * @param int $time 时间，单位秒
     * @return mixed|null
     */
    public static function set(string $key, $value, $time = 3600)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $value, $time) {
            return $redis->set($key, $value, $time);
        }, self::$name);
    }


    /**
     * 删除key
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:49 上午
     * @param string $key key
     * @return mixed|null
     */
    public static function del(string $key)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key) {
            return $redis->del($key);
        }, self::$name);
    }


    /**
     * key ttl
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:49 上午
     * @param string $key key
     * @return mixed|null
     */
    public static function ttl(string $key)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key) {
            return $redis->ttl($key);
        }, self::$name);
    }

    /**
     * keys
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:49 上午
     * @param string $key key
     * @return mixed|null
     */
    public static function keys(string $key)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key) {
            return $redis->keys($key);
        }, self::$name);
    }

    /********************************************** String ************************************************************/


    /********************************************** ZSet **************************************************************/

    /**
     * 添加数据到有序集合
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:49 上午
     * @param string $key key
     * @param $score 分值
     * @param $value 数据
     * @param array $data 更多数据
     * @return mixed|null
     */
    public static function zAdd(string $key, $score, $value, ...$data)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $score, $value, $data) {
            return $redis->zAdd($key, $score, $value, ...$data);
        }, self::$name);
    }


    /**
     * 获取有序集合，按照分值
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:51 上午
     * @param string $key key
     * @param $min 最小
     * @param $max 最大
     * @param bool[] $options 选项
     * @return mixed|null
     */
    public static function zRangeByScore(string $key, $min, $max, $options = ['withscores' => true])
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $min, $max, $options) {
            return $redis->zRangeByScore($key, $min, $max, $options);
        }, self::$name);
    }


    /**
     * 移除有序集合中的数据
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param array $member 成员
     * @return mixed|null
     */
    public static function zRem(string $key, ...$member)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $member) {
            return $redis->zRem($key, ...$member);
        }, self::$name);
    }

    /********************************************** ZSet **************************************************************/


    /**
     * redis+lua限流
     *
     * @param string $key key
     * @param int $limitNum 限流数
     * @param int $ttl 周期，单位秒
     * @return mixed|null
     * @date   2021/2/17
     * @time   1:50 下午
     * @author gan
     */
    public static function apiRateLimitByLua(string $key, int $limitNum, int $ttl = 5)
    {
        // $redis->eval($lua, 1, $key, $ttl, $score, $nonce, $limitNum);
        // 由于官方eval方法没开放，所以用rawCommand代替
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $limitNum, $ttl) {
            $lua = <<<SCRIPT
            redis.call('zAdd',KEYS[1],tonumber(ARGV[2]),ARGV[3])
            redis.call('zRemRangeByScore',KEYS[1],0,tonumber(ARGV[2])-tonumber(ARGV[1]))
            redis.call('expire',KEYS[1],tonumber(ARGV[1]))
            local num = redis.call('zCount',KEYS[1],0,tonumber(ARGV[2]))
            if num > tonumber(ARGV[4]) then
                return 1
            else
                return 0
            end
SCRIPT;

            $score = time();
            $nonce = uniqid() . (10000 * microtime(true));
            $rt    = $redis->rawCommand([
                'EVAL',
                $lua,
                1,
                $key,
                $ttl,
                $score,
                $nonce,
                $limitNum,
            ]);
            if ($rt === 1) {
                return false;
            }
            return true;
        }, self::$name);
    }


    /********************************************** Set ***************************************************************/

    /**
     * 添加集合中的数据
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param $value 数据
     * @param int $ttl ttl
     * @return mixed|null
     */
    public static function sAdd(string $key, $value, int $ttl = 0)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($ttl, $value, $key) {
            $rt = $redis->sAdd($key, ...$value);
            if ($rt && $ttl) {
                $redis->expire($key, $ttl);
            }
            return $rt;
        }, self::$name);
    }


    /**
     * 检测是否存在集合中
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param $value 数据
     * @return mixed|null
     */
    public static function sIsMember(string $key, $value)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($value, $key) {
            return $redis->sIsMember($key, $value);
        }, self::$name);
    }


    /**
     * 查询集合成员
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @return mixed|null
     */
    public static function sMembers(string $key)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key) {
            return $redis->sMembers($key);
        }, self::$name);
    }

    /********************************************** Set ***************************************************************/

    /**
     * 延迟双删回调
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param callable $callback 回调
     * @return mixed
     */
    public static function callbackDel(string $key, callable $callback)
    {
        self::del($key);
        defer(function () use ($key) {
            // 简单使用延迟双删
            usleep(100000);
            TaskManager::getInstance()->async(function () use ($key) {
                self::del($key);
            });
        });
        return call_user_func($callback);
    }


    /********************************************** HASH **************************************************************/

    /**
     * hash 多set
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param array $data 数据
     * @return mixed
     */
    public static function hMSet(string $key, array $data)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $data) {
            return $redis->hMSet($key, $data);
        }, self::$name);
    }


    /**
     * 检测字段是否存在
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param string $field 字段
     * @return mixed
     */
    public static function hExists(string $key, string $field)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $field) {
            return $redis->hExists($key, $field);
        }, self::$name);
    }


    /**
     * 获取值
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param array $fields 字段
     * @return mixed
     */
    public static function hMGet(string $key, array $fields)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $fields) {
            return $redis->hMGet($key, $fields);
        }, self::$name);
    }


    /**
     * 为哈希表 key 中的指定字段的整数值加上增量 increment
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2022/1/8 3:07 下午
     * @param string $key key
     * @param string $field hash中的字段
     * @param int $increment 值
     * @return mixed|null
     */
    public static function hIncrBy(string $key, string $field, int $increment)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $field, $increment) {
            return $redis->hIncrBy($key, $field, $increment);
        }, self::$name);
    }


    /**
     * 为哈希表 key 中的指定字段的浮点数值加上增量 increment
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2022/1/8 3:07 下午
     * @param string $key key
     * @param string $field hash中的字段
     * @param float $increment 值
     * @return mixed|null
     */
    public static function hIncrByFloat(string $key, string $field, float $increment)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $field, $increment) {
            return $redis->hIncrByFloat($key, $field, $increment);
        }, self::$name);
    }


    /**
     * 获取哈希表所有数据
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2022/1/8 3:07 下午
     * @param string $key key
     * @return mixed|null
     */
    public static function hGetAll(string $key)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key) {
            return $redis->hGetAll($key);
        }, self::$name);
    }


    /**
     * hash 设置
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2022/9/8 11:39 上午
     * @param string $key key
     * @param string $field 字段
     * @param $value 值
     * @return mixed|null
     */
    public static function hSet(string $key, string $field, $value)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $field, $value) {
            return $redis->hSet($key, $field, $value);
        }, self::$name);
    }


    /**
     * hash 删除
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2022/9/8 11:41 上午
     * @param string $key key
     * @param array $field 字段
     * @return mixed|null
     */
    public static function hDel(string $key, ...$field)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $field) {
            return $redis->hDel($key, ...$field);
        }, self::$name);
    }


    /**
     * 获取哈希表中字段的数量
     * @param string $key
     * @return mixed|null
     */
    public static function hLen(string $key)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key) {
            return $redis->hLen($key);
        }, self::$name);
    }

    /**
     * hash scan
     * @param string $key
     * @param $cursor
     * @param null $pattern
     * @param null $count
     * @return mixed|null
     */
    public static function hScan(string $key, &$cursor, $pattern = null, $count = null)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, &$cursor, $pattern, $count) {
            return $redis->hScan($key, $cursor, $pattern, $count);
        }, self::$name);
    }



    /********************************************** HASH **************************************************************/


    /********************************************** LIST **************************************************************/


    /**
     * 向列表最左边入栈
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param $value 数据
     * @return mixed
     */
    public static function lPush(string $key, $value)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $value) {
            return $redis->lPush($key, $value);
        }, self::$name);
    }


    /**
     * 向列表最右边出栈
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param int $num 数量
     * @return mixed
     */
    public static function rPop(string $key, int $num = 1)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $num) {
            if ($num == 1) {
                return $redis->rPop($key);
            } else {
                $redis->startPipe();
                $redis->lRange($key, 0, $num - 1);
                $redis->lTrim($key, $num, -1);
                return $redis->execPipe();
            }
        }, self::$name);
    }

    /********************************************** LIST **************************************************************/


    /********************************************** LOCK **************************************************************/


    /**
     * 加锁
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param int $ttl ttl
     * @return mixed
     */
    public static function lock(string $key, int $ttl = 5)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $ttl) {
            $randNum = RandomHelper::character(10);
            $result  = $redis->set($key, $randNum, ['NX', 'PX' => $ttl * 1000]);
            if ($result) {
                return $randNum;
            }
            return null;
        }, self::$name);
    }


    /**
     * 释放锁
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param string $randNum 锁标记
     * @param int $serialize 序列化类型
     * @return mixed
     */
    public static function unlock(string $key, string $randNum, int $serialize = RedisConfig::SERIALIZE_PHP)
    {
        if ($serialize == RedisConfig::SERIALIZE_PHP) {
            $randNum = serialize($randNum);
        }

        return RedisPool::invoke(function (ESRedis $redis) use ($key, $randNum) {
            $lua = <<<LUA
if redis.call('get',KEYS[1]) == ARGV[1] then 
   return redis.call('del',KEYS[1]) 
else
   return 0 
end
LUA;
            return $redis->rawCommand(['EVAL', $lua, 1, $key, $randNum]);
        }, self::$name);
    }

    /**
     * 加锁callback
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param callable $callback 回调
     * @param int $ttl 默认时间，单位秒
     * @return mixed
     */
    public static function lockFunction(string $key, callable $callback, $ttl = 5)
    {
        $random = self::lock($key, $ttl);
        if ($random === null) {
            return false;
        }
        $result = call_user_func($callback);
        self::unlock($key, $random);
        return $result;
    }

    /********************************************** LOCK **************************************************************/


    /********************************************** Stream ************************************************************/

    /**
     * 向流中添加数据
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param array $message 数据
     * @param int|null $maxLen 最大长度
     * @param string $id id 格式   x-x ，一定有序性
     * @param bool $isApproximate
     * @return mixed
     */
    public static function xAdd(
        string $key,
        array $message,
        int $maxLen = null,
        string $id = "*",
        bool $isApproximate = false
    ) {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $id, $message, $maxLen, $isApproximate) {
            return $redis->xAdd($key, $id, $message, $maxLen, $isApproximate);
        }, self::$name);
    }


    /**
     * 长度
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @return mixed|null
     */
    public static function xLen(string $key)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key) {
            return $redis->xLen($key);
        }, self::$name);
    }


    /**
     * 读取流中的数据
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param array $streams 流
     * @param int $count 数量
     * @param null $block 是否堵塞，如果数字的话，代表堵塞多久
     * @return mixed|null
     */
    public static function xRead(array $streams, int $count = 1, $block = null)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($streams, $count, $block) {
            return $redis->xRead($streams, $count, $block);
        }, self::$name);
    }


    /**
     * 读取范围数据
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 9:52 上午
     * @param string $key key
     * @param string $start 开始
     * @param string $end 结束
     * @param int $count 数量
     * @return mixed|null
     */
    public static function xRange(string $key, string $start = '-', string $end = '+', int $count = 1)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $start, $end, $count) {
            return $redis->xRange($key, $start, $end, $count);
        }, self::$name);
    }

    /**
     * 删除
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 10:06 上午
     * @param string $key key
     * @param array $ids 数据
     * @return mixed|null
     */
    public static function xDel(string $key, array $ids)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $ids) {
            return $redis->xDel($key, $ids);
        }, self::$name);
    }

    /**
     * group操作
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 10:07 上午
     * @param string $operation 操作，CREATE，SETID等
     * @param string $key key
     * @param string $group 组名
     * @param string $msgId 消息ID
     * @param bool $mkStream 流
     * @return mixed|null
     */
    public static function xGroup(
        string $operation,
        string $key = '',
        string $group = '',
        string $msgId = '$',
        bool $mkStream = false
    ) {
        return RedisPool::invoke(function (ESRedis $redis) use ($operation, $key, $group, $msgId, $mkStream) {
            return $redis->xGroup($operation, $key, $group, $msgId, $mkStream);
        }, self::$name);
    }


    /**
     * 查看流和消费者组的相关信息
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 10:09 上午
     * @param string $operation 操作
     * @param string $key key
     * @param string $group 组名
     * @return mixed|null
     */
    public static function xInfo(string $operation, string $key = '', string $group = '')
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($operation, $key, $group) {
            return $redis->xInfo($operation, $key, $group);
        }, self::$name);
    }


    /**
     * 读取消费者组中的消息
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 10:09 上午
     * @param string $group 操作
     * @param string $consumer 消费者
     * @param array $streams 流
     * @param int $count 数量
     * @param null $block 是否堵塞，同xRead一样
     * @return mixed|null
     */
    public static function xReadGroup(string $group, string $consumer, array $streams, $count = 1, $block = null)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($group, $consumer, $streams, $count, $block) {
            return $redis->xReadGroup($group, $consumer, $streams, $count, $block);
        }, self::$name);
    }


    /**
     * ack
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 10:09 上午
     * @param string $key key
     * @param string $group 组名
     * @param array $ids 消息ID
     * @return mixed|null
     */
    public static function xAck(string $key, string $group, array $ids = [])
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $group, $ids) {
            return $redis->xAck($key, $group, $ids);
        }, self::$name);
    }


    /**
     * 显示待处理消息的相关信息
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 10:11 上午
     * @param string $key key
     * @param string $group 组
     * @param null $consumer 消费者
     * @param int $count 数量
     * @param string $start 开始
     * @param string $end 结束
     * @return mixed|null
     */
    public static function xPending(
        string $key,
        string $group,
        $consumer = null,
        $count = 1,
        string $start = '-',
        string $end = '+'
    ) {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $group, $start, $end, $count, $consumer) {
            return $redis->xPending($key, $group, $start, $end, $count, $consumer);
        }, self::$name);
    }


    /**
     * 转移消息的归属权
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 10:11 上午
     * @param string $key key
     * @param string $group 组
     * @param string $consumer 消费者
     * @param int $minIdleTime 消息的空闲时间
     * @param array $ids 消息ID
     * @param array $options 选项
     * @return mixed|null
     */
    public static function xClaim(
        string $key,
        string $group,
        string $consumer,
        int $minIdleTime,
        array $ids,
        array $options = []
    ) {
        return RedisPool::invoke(function (ESRedis $redis) use ($key, $group, $consumer, $minIdleTime, $ids, $options) {
            return $redis->xClaim($key, $group, $consumer, $minIdleTime, $ids, $options);
        }, self::$name);
    }

    /********************************************** Stream ************************************************************/


    /********************************************** Script ************************************************************/

    /**
     * 自定义脚本
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2021/12/29 10:11 上午
     * @param array $params 自定义命令
     * @return mixed|null
     */
    public static function rawCommand(array $params)
    {
        return RedisPool::invoke(function (ESRedis $redis) use ($params) {
            return $redis->rawCommand($params);
        }, self::$name);
    }

    /********************************************** Script ************************************************************/
}
